/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.helper;

import java.util.Iterator;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.BehavioredClassifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.UMLFactory;

import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.Activator;
import com.cea.papyrus.diagram.statemachine.command.AddRegionNodeCommand;
import com.cea.papyrus.diagram.statemachine.command.ResizeRegionNodeCommand;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagram.statemachine.preference.StateMachineDiagramPreferenceConstants;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * Manages state machine.
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class NodeStateMachineHelper extends NodeHelper{

	/**
	 * The channel.
	 */
	private int channel = 501;

	/**
	 * This method creates GraphNodes for a state machine and its embedded regions.
	 * A default size is given to the state machine. Embedded regions are presented in a vertical fashion
	 * with equal size. This is done via the execution of ResizeRegionNodeCommand following each
	 * addition of a region.
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createGraphElement(org.eclipse.uml2.uml.Element, com.cea.papyrus.diagraminterchange2.di2.GraphElement, org.eclipse.draw2d.geometry.Point, org.eclipse.draw2d.geometry.Dimension)
	 */
	@Override
	public GraphNode createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size){
		Debug.debug(this,"Call GraphNode NodeStateMachineHelper.createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size)", channel); //$NON-NLS-1$

		int defaultY = 20;
		int defaultSize = 250;
		
		if((size == null) || size.equals(getGraphNodeSize()))
			//provide a default size for the state machine node
			size = new Dimension(defaultSize, defaultSize);
		//caution: must pass a null argument for umlParent to the super class method
		//otherwise the graph node is directly added!!
		GraphNode aStateMachineNode = super.createGraphElement(umlChild, null, location, size);
		//retrieve the default region that was created at the time of UML StateMachine element 
		Region aRegion = (Region)((StateMachine)umlChild).getRegions().get(0);
		
		//create a node for this default region with correct position and size
		GraphNode aRegionNode = super.createGraphElement(aRegion, aStateMachineNode, new Point(0, defaultY), new Dimension(size.width, size.height-defaultY));

		//sets its semantic parent
		aRegionNode.setSemanticParent(aStateMachineNode);
		
		//assign default values for zone and dropLocation properties
		Property prop = Di2Factory.eINSTANCE.createProperty();
		prop.setKey("zone");
		prop.setValue(Zone.NONE);
		aRegionNode.getProperty().add(prop);

		prop = Di2Factory.eINSTANCE.createProperty();
		prop.setKey("dropLocation");
		prop.setValue(Zone.NONE);
		aRegionNode.getProperty().add(prop);
		
		GraphNode labelNode = new NodeHelper().createGraphElement(null, null, new Point(0, 0), null);
		labelNode.setSemanticParent(aRegionNode);
	    labelNode.setContainer(aRegionNode);
	    labelNode.setIsVisible(false);

	    //iterate on the list of regions
		Iterator regions = ((StateMachine)umlChild).getRegions().iterator();
		//calculate an equal size for the regions
		int nRegions = ((StateMachine)umlChild).getRegions().size();
		int equalSize = (int)Math.floor(defaultSize/nRegions);
		
		//pass the first default region
		regions.next();
		GraphNode previousRegionNode = aRegionNode;
		while(regions.hasNext()){
			//create a new Region node
			//Note: the null argument for the parent node; this region will not be attached directly to the statemachine node
			//but to a splitter node when executing the AddREgionNodeCommand
			GraphNode newRegionNode = super.createGraphElement((Region)regions.next(), null, null, new Dimension(size.width, size.height-defaultY));
			//assign default values for zone and dropLocation properties; a RIGHT drop location is set so that regions will be created in horizontal row
			prop = Di2Factory.eINSTANCE.createProperty();
			prop.setKey("zone");
			prop.setValue(Zone.NONE);
			newRegionNode.getProperty().add(prop);

			prop = Di2Factory.eINSTANCE.createProperty();
			prop.setKey("dropLocation");
			prop.setValue(Zone.RIGHT);
			newRegionNode.getProperty().add(prop);
			
			labelNode = new NodeHelper().createGraphElement(null, null, new Point(0, 0), null);
			labelNode.setSemanticParent(newRegionNode);
		    labelNode.setContainer(newRegionNode);
		    labelNode.setIsVisible(false);

			//Note: do not set the semantic link now, it will be done later in the AddRegionCommand
			//this serves to distinguish between a region created and added (no semantic link set) and a region simply dragged and dropped (semantic link set)
			
			//create an AddRegionNodeCommand and execute it
			AddRegionNodeCommand addRegionNodeCommand = new AddRegionNodeCommand(newRegionNode, previousRegionNode);
			addRegionNodeCommand.execute();
			
			//compute a fake ResizeCommand so that all regions have same width
			ChangeBoundsRequest request = new ChangeBoundsRequest();
			request.setType(RequestConstants.REQ_RESIZE);
			request.setResizeDirection(PositionConstants.WEST);
			int move = newRegionNode.getSize().width-equalSize;
			request.setSizeDelta(new Dimension(move, 0));
			ResizeRegionNodeCommand resizeCommand = new ResizeRegionNodeCommand(newRegionNode, request);
			//execute the ResizeCommand
			resizeCommand.execute();
			
			//update previous region node
			previousRegionNode = newRegionNode;
		}
		//the ge parent is connected in last because an event may be sent at this moment and trigger a refresh.
		if(parent != null)
			parent.getContained().add(aStateMachineNode);
		return aStateMachineNode;
	}

	/**
	 * 
	 * 
	 * @param parent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateGraphElement(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public boolean canCreateGraphElement(GraphElement parent){
		Debug.debug(this,"Call boolean NodeStateMachineHelper.canCreateGraphElement(GraphElement parent)", channel);

		if(parent instanceof Diagram){
			return true;
		}
		else if(parent.getSemanticModel() != null){
			Element parentElt = ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
			if(parentElt != null)
				return canCreateUMLElement(parentElt);
		}
		return false;
	}

	///////////////////////////////////////////////////////////////////////////
	// UML Management
	///////////////////////////////////////////////////////////////////////////

	/**
	 * 
	 * 
	 * @param umlParent 
	 * @param name 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createUMLElement(java.lang.String, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public Element createUMLElement(String name, Element umlParent){
		Debug.debug(this,"Call Element NodeStateMachineHelper.createUMLElement(String name, Element umlParent)", channel); //$NON-NLS-1$

		//create a StateMachine with the provided name
		StateMachine aStateMachine = UMLFactory.eINSTANCE.createStateMachine();
		aStateMachine.setName(name);

		//create a default Region linked to this StateMachine
		//use of NodeRegionHelper for the name (same remark as above)
		Region aRegion = (Region)new NodeRegionHelper().createUMLElement(aStateMachine);

		//finally link the StateMachine with its parent BehavioredClassifier
		((BehavioredClassifier)umlParent).getOwnedBehaviors().add(aStateMachine);
		//test this later
		//((BehavioredClassifier)umlParent).setClassifierBehavior(aStateMachine);
		return aStateMachine;
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateUMLElement(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean canCreateUMLElement(Element umlParent){
		Debug.debug(this,"Call boolean NodeStateMachineHelper.canCreateUMLElement(Element umlParent)", channel); //$NON-NLS-1$

		//we want to prevent the creation of sub statemachines
		if((umlParent instanceof BehavioredClassifier) && !(umlParent instanceof StateMachine))
			return true;
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#addUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean addUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call boolean NodeStateMachineHelper.addUMLElement(Element element, Element umlParent)", channel); //$NON-NLS-1$

		if(umlParent instanceof BehavioredClassifier)
			return ((BehavioredClassifier)umlParent).getOwnedBehaviors().add((Behavior)element);
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#removeElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean removeUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call boolean NodeStateMachineHelper.removeUMLElement(Element element, Element umlParent)", channel); //$NON-NLS-1$

		if(umlParent instanceof BehavioredClassifier)
			return ((BehavioredClassifier)umlParent).getOwnedBehaviors().remove(element);
		return false;
	}

	/**
	 * 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getBaseString()
	 */
	@Override
	public String getBaseString(){
		return "StateMachine_"; //$NON-NLS-1$
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getNewUMLElementName(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public String getNewUMLElementName(Element umlParent){
		String name = ""; //$NON-NLS-1$

		//i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			name = getBaseString()+i;
			if(umlParent == null)
				return name;
			if(((BehavioredClassifier)umlParent).getOwnedBehavior(name) == null)
				return name;
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}


	/**
	 * 
	 * 
	 * @param graphElement 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#setGraphElementPreferences(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public void setGraphElementPreferences(GraphElement graphElement){
		//get the preference store
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();

		//set colors and font
		graphElement.setBackgroundColor(StateMachineDiagramPreferenceConstants.getPreferenceStateMachineBackgroundColor(store));
		graphElement.setBorderColor(StateMachineDiagramPreferenceConstants.getPreferenceStateMachineBorderColor(store));
		graphElement.setFontColor(StateMachineDiagramPreferenceConstants.getPreferenceStateMachineFontColor(store));
		FontData[] fontdata = StateMachineDiagramPreferenceConstants.getPreferenceStateMachineFont(store);
		if(fontdata.length>0){
			graphElement.setFontFamily(fontdata[0].getName());
			graphElement.setFontSize(fontdata[0].getHeight());
		}
	}
}